# -*- coding:utf-8 -*-
import json
from Env import env_config as cfg
from Util.date_helper import get_current_iso_date
import re
from Core.verify_interface import VerifyInterface as vi
from Common.model import db_model
from Util.tool import capture_str_variable
from Common.com_func import is_null, json_path_value, log
from io import StringIO


class AcquireDependField(object):
    """
     【 获 取 依 赖 字 段 值 】
     0.获取'测试接口列表'中的'参数依赖字段名列表'（去重）( 接口地址、请求头文件、请求参数 )
       < 根据 '参数依赖字段名列表' 判断是否需要 执行依赖接口 >
     1.<判断> 是否存在依赖接口
      （1）若不存在，则'整体记录' < error:依赖接口不存在 >
      （2）若存在，则 继续
     2.获取'依赖接口列表'中的'依赖字段名列表'，并清空相关结果记录
     3.<判断> '测试接口列表'中的依赖字段是否都包含在'依赖接口列表'中的依赖字段里面
      （1）若存在不包含的情况，则'整体记录' < error:依赖字段名配置有遗漏(all) >
         （ 由于没有进行请求,所以需要给每个用例的"response_info"设置为空 ）
      （2）若全包含，则 继续
     4.'依赖接口列表'按照依赖等级排序
     5.循环发送'依赖接口列表'中的请求
      （1）替换'依赖接口'中的'依赖变量'（依赖接口也存在依赖关系）
      （2）转换 '请求参数'或'请求头文件' 格式类型 <判断>（ 将 mongo 中的 str 类型 转成 需要的类型 ）
            若转换失败，'分开记录'< error:依赖接口'请求参数'或'请求头文件'格式有误 >
            若转换成功 <判断> 接口调用情况及响应码
            1）接口调用异常 '分开记录' < fail:依赖接口异常 >
            2）接口调用无异常
                1）无响应：'分开记录' < fail:依赖接口无响应 >
                2）有相应（ http != 200 ）：'分开记录'< fail:依赖接口错误,http_code<500>,原因解析(Internal Server Error)" >
                3）有响应（ http == 200 ）：'分开记录'< success:依赖通过 >、
                     捕获'依赖字段值'(响应体重捕获、响应头中捕获)、获取'依赖接口'中的'依赖字段值列表'并判断依赖字段是否获取到
     6.获取'依赖接口'执行失败的结果列表 < 判断 >
      （1）若存在'fail'，则不做处理，保持原有结果记录
      （2）若全部是'success'，则 < 判断 > '依赖字段值'是否全部都获取到
           1）是，则 替换'测试接口列表'中的'依赖字段变量'（依赖字段捕获成功）
           2）否，则 '整体记录' < error:依赖字段值没有全部获取到 >
     7.若存在(all)的失败用例,则需要给每个用例的"test_result"设置为该(all)结果
     8.更新'依赖接口列表'、'测试接口列表'结果
        < 判断 > 测试接口列表
       （1）若 '依赖接口'测试结果 全部是'success' 则 不更新 '测试接口列表' 结果
       （2）若 '依赖接口'测试结果 存在'error'或'fail' 则 所有的 '测试接口列表' 结果 都更新（选择第一个结果进行保存）
               并清除相关'测试记录'内容，同时改变'接口验证标记'（表示：不需要在验证测试接口）
     RETURN：test_interface_list

       < 依 赖 接 口 test_result >
        01.success:依赖通过
        02.fail:依赖接口无响应
        03.error:依赖接口响应信息格式不是JSON(all)
        04.fail:依赖接口错误,http_code<500>,原因解析(Internal Server Error)
        05.error:依赖字段没有获取到
        06.error:依赖字段名配置有遗漏(all)
        07.error:依赖字段值没有全部获取到(all)
        08.error:依赖接口'请求参数'或'请求头文件'格式有误
        09.error:依赖接口上传的文件不存在
        00.error:依赖接口上传的文件不能为空
        11.error:依赖接口请求头不能为空
        12.error:依赖接口请求头中的'Content-Type'类型暂不支持
        13.error:依赖接口请求头中的'Content-Type'字段不存在
        14.error:依赖接口上传文件接口的请求头不能包含'Content-Type'
        15.error:依赖字段索引'x'超出了范围(0-n)
        16.error:依赖字段不存在


        [ 备 注 ]
        1.'error:依赖接口不存在(all)'不记录在'依赖接口 test_result'中，而是记录在'测试接口 test_result'中
        2.依赖接口列表 整体记录 的 'test_result'
        （01）error:依赖接口不存在(all)
        （02）error:依赖字段名配置有遗漏(all)
        （03）error:依赖字段值没有全部获取到(all)
        （04）error:依赖接口响应信息格式不是JSON(all)
        3.依赖接口列表 分开记录 的 'test_result':
        （01）success:依赖通过
        （02）fail:依赖接口调用异常
        （03）fail:依赖接口无响应
        （04）fail:依赖接口错误,http_code<500>,原因解析(Internal Server Error)
        （05）error:依赖接口'请求参数'或'请求头文件'格式有误
        （06）error:依赖字段没有获取到
        （07）error:依赖接口上传的文件不存在
        （08）error:依赖接口上传的文件不能为空
        （09）error:依赖接口请求头不能为空
        （10）error:依赖接口请求头中的'Content-Type'类型暂不支持
        （11）error:依赖接口请求头中的'Content-Type'字段不存在
        （12）error:依赖接口上传文件接口的请求头不能包含'Content-Type'

        举例：
        1.整体记录：["error:依赖字段名配置有遗漏(all)", "error:依赖字段名配置有遗漏(all)"]
        2.分开记录：["success:依赖通过", "fail:依赖接口错误xxx"]

        《 捕获 依赖字段 逻辑 》
        举例：self.depend_field_list = [ "token::H", "uid:H", "image_id" ]
        说明：带有'::H'需要在响应头中进行捕获，其余在响应体中进行捕获
        步骤：
            1.获取依赖字段名列表： 存入 self.depend_field_list 属性
            2.捕获的依赖字段键值对(响应头)：存入 self.capture_depend_field_dict 属性
            3.捕获的依赖字段键值对(响应体)：存入 self.capture_depend_field_dict 属性
            4.获取'依赖接口'中的'依赖字段值列表'：调用 self.get_depend_field_value()方法
    """

    def __init__(self, pro_name, host, depend_interface_list, test_interface_list):
        self.pro_name = pro_name
        self.host = host
        self.response_info_list = []
        self.depend_interface_list = depend_interface_list  # 上线的'依赖接口列表'（按照依赖等级顺序排列）
        self.test_interface_list = test_interface_list      # 上线的'测试接口列表'
        self.params_depend_field_list = []                  # '测试接口列表'中的'依赖字段名列表'（所有测试接口中捕获的依赖字段）
        self.depend_field_list = []                         # '依赖接口列表'中的'依赖字段名列表'（所有依赖接口中设置的依赖字段）
        self.depend_interface_result_list = []              # '依赖接口列表'执行结果 ['fail:依赖接口无响应', 'success:依赖通过']
        self.capture_depend_field_dict = {}                 # 捕获的依赖字段键值对 {"token::H":"xxxxx", "image_id":"xxxxx"}
        self.verify_flag = True                             # 接口测试标记 True：需要验证、False：不需要验证

    def get_params_depend_field_list(self):
        """
        获取 '测试接口列表'中的'依赖字段名列表'（去重、排序）
        ( 接口地址、请求头文件、请求参数 )
        """
        for index, test_interface_dict in enumerate(self.test_interface_list):
            for key, value in test_interface_dict.items():
                if key in ["interface_url", "request_header", "request_params", "request_body"]:
                    capture_field_list = capture_str_variable(str=value, v_tag="{{")
                    self.params_depend_field_list.extend(capture_field_list)
        self.params_depend_field_list = list(set(self.params_depend_field_list))
        self.params_depend_field_list.sort()

    def replace_params(self, interface_dict):
        """
        替换 '接口字典'中的'依赖字段变量' ( 接口地址、请求头文件、请求参数、请求正文体 )
        :param interface_dict  测试接口字典 | 依赖接口字典
        :return:
        """
        for key in interface_dict.keys():
            # 替换 接口中的'依赖字段'变量
            if self.capture_depend_field_dict and key in ["interface_url", "request_header", "request_params", "request_body"]:
                for field, value in self.capture_depend_field_dict.items():
                    interface_dict[key] = interface_dict[key].replace("{{" + field + "}}", value)

    def get_depend_field_value(self, depend_interface_dict):
        """
        获取 '依赖接口' 中的 '依赖字段值列表'
        :param depend_interface_dict:
        :return:
          判断 字段值列表数量 是否与 字段名列表数量 一致
        """
        depend_interface_dict["depend_field_value_list"] = \
            list(map(lambda x: x[1], filter(lambda k_v: k_v[0] in depend_interface_dict["depend_field_name_list"],
                                            self.capture_depend_field_dict.items())))
        return len(depend_interface_dict["depend_field_value_list"]) == len(depend_interface_dict["depend_field_name_list"])

    def is_need_depend(self):
        """
        判断 是否需要依赖
        :return:
        """
        self.get_params_depend_field_list()
        return self.params_depend_field_list != [] or False

    def capture_denpend_by_headers(self, sess, depend_interface_dict):
        """
            从'响应头'中捕获依赖字段 （ 含有'::H'的需要捕获 ）
        :param sess:
        :param depend_interface_dict:
        :return:
        """
        depend_field_header_list = list(filter(lambda x: "::H" in x, depend_interface_dict["depend_field_name_list"]))
        if depend_field_header_list:
            for field in depend_field_header_list:
                self.capture_depend_field_dict[field] = sess.cookies.get(field.replace("::H", ""))

    def capture_denpend_by_body(self, response_dict, depend_interface_dict):
        """
        从'响应体'中捕获依赖字段 （ 含有非'::H'的需要捕获 ）
        :param response_dict:  响应信息 字典
        :param depend_field_body_list: 待捕获的 响应体中的字段列表
            举例：
            捕获第1个值 -> token
            捕获第3个值 -> token::3
            捕获最后1个值 -> token::0
        """
        error_msg = ""
        depend_field_body_list = list(filter(lambda x: "::H" not in x, depend_interface_dict["depend_field_name_list"]))
        if depend_field_body_list:
            # 方式一：递归捕获'依赖字段值'（ 暂时不用 ）
            # self.recur_capture_depend(response_dict)

            # 方式二：jsonpath捕获
            for field in depend_field_body_list:
                if field in self.depend_field_list:
                    tmp = field.split("::")
                    json_path = tmp[0]
                    field_index = len(tmp) == 2 and int(tmp[1])-1 or 0
                    value = json_path_value(data_dict=response_dict, json_path=json_path, field_index=field_index)
                    if "超出了最大范围" in value:
                        error_msg = f"error:依赖字段{value}"
                        break
                    elif "json字段取值失败" in value:
                        error_msg = f"error:依赖字段'{field}'不存在"
                    else:
                        self.capture_depend_field_dict[field] = value
        return error_msg

    def recur_capture_depend(self, response_dict):
        """
        递归 捕获 依赖字段 ( 暂时不用，由jsonpath取代 )
        1.捕获 相应信息中需要的字段值
        2.若存在相同字段名的，取最后一个（默认）
        """
        if isinstance(response_dict, dict):
            for key, value in response_dict.items():
                if isinstance(value, list):
                    for ele in value:
                        self.recur_capture_depend(ele)
                elif isinstance(value, dict):
                    self.recur_capture_depend(value)
                else:
                    if key in self.depend_field_list:
                        self.capture_depend_field_dict[key] = value

    def acquire(self):
        """
            获取 依赖字段值
            < 主 要 步 骤 > '依赖接口列表' -> 替换'依赖接口列表'依赖字段 > 执行 > 捕获依赖变量 > 替换'测试接口列表'依赖字段
        :return:
        """
        # 1.判断是否存在依赖接口
        if is_null(self.depend_interface_list):
            self.depend_interface_result_list = ["error:依赖接口不存在(all)"]
        else:
            # 2.获取'依赖接口列表'中的'依赖字段名列表'，并清空相关结果记录
            for depend_interface_dict in self.depend_interface_list:
                self.depend_field_list += depend_interface_dict["depend_field_name_list"]
                depend_interface_dict["test_result"] = ""
                depend_interface_dict["response_info"] = ""
                depend_interface_dict["depend_field_value_list"] = ""
            # 去重、排序
            # self.depend_field_list = list(set(self.depend_field_list))
            # self.depend_field_list.sort()

            # 3.判断 '测试接口列表'中的依赖字段是否都包含在'依赖接口列表'中的依赖字段里面
            no_contain_list = list(filter(lambda x: x not in self.depend_field_list, self.params_depend_field_list))
            if no_contain_list:
                self.response_info_list.append("")
                # 由于没有进行请求,所以需要给每个用例的"response_info"设置为空
                # 将需要整体记录的'response_info'，按照依赖列表个数赋值给'response_info_list'
                self.response_info_list = self.response_info_list * len(self.depend_interface_list)
                self.depend_interface_result_list = ["error:依赖字段名配置有遗漏(all)"]
            else:
                # 4.'依赖接口列表'按照依赖等级排序
                self.depend_interface_list = sorted(self.depend_interface_list, key=lambda keys: keys['depend_level'])

                # 5.循环发送'依赖接口列表'中的请求
                for depend_interface_dict in self.depend_interface_list:

                    # 替换'依赖接口'中的'依赖变量'（依赖接口也存在依赖关系）
                    self.replace_params(depend_interface_dict)

                    # 转换 '请求头文件、请求参数、请求正文体' 格式类型（ 将 mongo 中的 str 类型 转成 需要的类型 ）
                    error_msg, file, depend_interface_dict["request_params"], \
                        depend_interface_dict["request_header"], depend_interface_dict["request_body"] = \
                        vi.transform_params_format(request_params=depend_interface_dict["request_params"],
                                                   request_header=depend_interface_dict["request_header"],
                                                   request_body=depend_interface_dict["request_body"])
                    if error_msg:
                        self.response_info_list.append("")
                        self.depend_interface_result_list.append("error:依赖接口" + error_msg)
                    else:
                        try:
                            res, sess = \
                                vi.send_request(request_method=depend_interface_dict["request_method"],
                                                interface_url=self.host + depend_interface_dict["interface_url"],
                                                request_params=depend_interface_dict["request_params"],
                                                request_header=depend_interface_dict["request_header"],
                                                request_body=depend_interface_dict["request_body"], file=file)
                        except Exception:
                            self.response_info_list.append("")
                            self.depend_interface_result_list.append("fail:依赖接口调用异常")
                        else:
                            if res == 31500:
                                self.response_info_list.append("")
                                self.depend_interface_result_list.append("fail:依赖接口无响应")
                            elif res.status_code not in [200, 302]:
                                msg = re.search(r'<title>(.*?)</title>', res.text)
                                test_result = ""
                                try:
                                    reason = msg.group(1)
                                    test_result = f"fail:依赖接口错误,http_code<{str(res.status_code)}>原因解析({reason})"
                                except Exception:
                                    test_result = f"fail:依赖接口错误,http_code<{str(res.status_code)}>"
                                finally:
                                    self.response_info_list.append(res.text)
                                    self.depend_interface_result_list.append(test_result)
                            else:
                                # 从'响应头'中捕获依赖字段
                                self.capture_denpend_by_headers(sess, depend_interface_dict)

                                # 从'响应体'中捕获依赖字段（ 判断响应是否为JSON格式 ）
                                if res.text.startswith("{") and res.text.endswith("}"):
                                    depend_interface_result = self.capture_denpend_by_body(json.loads(res.text),
                                                                                           depend_interface_dict)
                                else:
                                    depend_interface_result = "error:依赖接口响应信息格式不是JSON(all)"

                                # 获取'依赖接口'中的'依赖字段值列表' 并判断依赖字段是否获取到
                                if depend_interface_result == "":
                                    is_capture = self.get_depend_field_value(depend_interface_dict)
                                    depend_interface_result = is_capture and "success:依赖通过" or "error:依赖字段没有获取到"

                                # 保存当前依赖接口的相关信息
                                self.response_info_list.append(str(json.loads(res.text)))  # 转换 响应信息 （ 防止中文乱码 ）
                                self.depend_interface_result_list.append(depend_interface_result)

                # 6.获取'依赖接口'执行失败的结果列表 < 判断 >
                fail_result_list = list(filter(lambda x: "success" not in x, self.depend_interface_result_list))
                # 若全部是 'success'，则 < 判断 > '依赖字段值'是否全部都获取到
                if not fail_result_list:
                    capture_depend_field_list = self.capture_depend_field_dict.keys()
                    no_capture_list = list(filter(lambda x: x not in capture_depend_field_list, self.params_depend_field_list))
                    if no_capture_list:
                        self.depend_interface_result_list = ["error:依赖字段值没有全部获取到(all)"]
                    else:
                        # 替换'测试接口列表'中的'依赖字段变量'（依赖字段捕获成功）
                        for test_interface_dict in self.test_interface_list:
                            self.replace_params(test_interface_dict)

            # 7.若存在(all)的失败用例,则需要给每个用例的"test_result"设置为该(all)结果
            #   将需要整体记录的'test_result'，按照依赖列表个数赋值给'depend_interface_result_list'
            # error_result = [result for result in self.depend_interface_result_list if "(all)" in result]
            error_result = list(filter(lambda x: "(all)" in x, self.depend_interface_result_list))
            if error_result:
                self.depend_interface_result_list = self.depend_interface_result_list * len(self.depend_interface_list)

        # 8.更新'依赖接口列表'、'测试接口列表'结果
        update_time = get_current_iso_date()
        self.update_depend_interface_list_result(update_time)
        self.update_test_interface_list_result(update_time)

        # 显示相关变量字段（调试少量用例时使用）
        # self.debug_variable_field()

        return self.test_interface_list, update_time

    def update_depend_interface_list_result(self, update_time):
        """
        更新'依赖接口列表'结果
        （1）test_result：依赖测试结果
        （2）depend_field_value_list：依赖字段值列表
        :return:
        """
        dm = db_model(pro_name=self.pro_name, table=cfg.TABLE_CASE, error_msg="更新依赖接口列表结果")
        for index, depend_interface_dict in enumerate(self.depend_interface_list):
            query_dict = {"_id": depend_interface_dict["_id"]}
            set_dict = {"update_time": update_time, "test_result": self.depend_interface_result_list[index],
                        "depend_field_value_list": depend_interface_dict["depend_field_value_list"],
                        "response_info": self.response_info_list[index]}
            dm.update_db(query=query_dict, set_dict=set_dict, is_many=False)

    def update_test_interface_list_result(self, update_time):
        """
        更新'测试接口列表'结果
        （1）若 '依赖接口'测试结果 全部是'success' 则 不更新 '测试接口列表' 结果
        （2）若 '依赖接口'测试结果 存在'error'或'fail' 则 所有的 '测试接口列表' 结果 都更新（选择第一个结果进行保存）
                并清除相关'测试记录'内容，同时改变'接口验证标记'（表示：不需要在验证测试接口）
        """
        wang_result = list(filter(lambda x: "error" in x or "fail" in x, self.depend_interface_result_list))
        if wang_result:
            self.verify_flag = False
            dm = db_model(pro_name=self.pro_name, table=cfg.TABLE_CASE, error_msg="更新测试接口列表结果")
            for index, test_interface_dict in enumerate(self.test_interface_list):
                query_dict = {"_id": test_interface_dict["_id"]}
                set_dict = {"update_time": update_time, "test_result": wang_result[0], "response_info": "",
                            "actual_core_field_value_list": [], "actual_field_name_list": [],
                            "downstream_relation_field_value_list": [], "result_core_field_value": "",
                            "result_field_name_list": [], "http_code": -1, "http_rt": -1}
                dm.update_db(query=query_dict, set_dict=set_dict, is_many=False)

    def debug_variable_field(self):
        """
        显 示 相 关 变 量 字 段
          < 注意：仅在执行 少量 用例 时使用 >
        :return:
        """
        print("\n=============================================================\n")
        print("依赖接口中设置的依赖字段 -> " + str(self.depend_field_list))
        print("测试接口中捕获的依赖字段 -> " + str(self.params_depend_field_list))
        print("捕获的依赖字段键值对 -> " + str(self.capture_depend_field_dict))
        print("依赖接口执行结果列表 -> " + str(self.depend_interface_result_list))
        print("\n===========================[ depend_interface_list ]==================================\n")
        for index, depend_interface_dict in enumerate(self.depend_interface_list):
            print("depend_level  " + str(depend_interface_dict["depend_level"]))
            print("interface_name  " + depend_interface_dict["interface_name"])
            print("interface_url  " + depend_interface_dict["interface_url"])
            print("request_method  " + depend_interface_dict["request_method"])
            print("request_header  " + str(depend_interface_dict["request_header"]))
            print("request_params  " + str(depend_interface_dict["request_params"]))
            print("request_body  " + str(depend_interface_dict["request_body"]))
            print("-----")
        print("\n===========================[ test_interface_list ]==================================\n")
        for index, test_interface_dict in enumerate(self.test_interface_list):
            print("interface_name  " + test_interface_dict["interface_name"])
            print("interface_url  " + test_interface_dict["interface_url"])
            print("request_method  " + test_interface_dict["request_method"])
            print("request_header  " + str(test_interface_dict["request_header"]))
            print("request_params  " + str(test_interface_dict["request_params"]))
            print("request_body  " + str(test_interface_dict["request_body"]))
            print("-----")
